package fr.afcepf.atod.util.dao;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import javax.sql.DataSource;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.omg.CORBA.portable.ApplicationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class SimpleDao extends HibernateDaoSupport implements IDao {

	private static Logger logger = LoggerFactory.getLogger(SimpleDao.class);

	private final Class<?> m_beanClass;

	private DataSource m_slaveDataSource;

	private boolean masterDS = true;

	/**
	 * Construit un DAO pour le bean dont la classe est passée en paramétre
	 * 
	 * @param _beanClass
	 *            Classe du bean géré par le DAO
	 */
	protected SimpleDao(final Class<?> _beanClass) {
		m_beanClass = _beanClass;
	}

	/**
	 * Retourne la classe du bean géré par le DAO
	 * 
	 * @return la classe du bean géré par le DAO
	 */
	protected Class<?> getClassBean() {
		return m_beanClass;
	}

	/**
	 * Retourne le logger de la classe
	 * 
	 * @return le logger de la classe
	 */
	protected Logger getLog() {
		return logger;
	}

	@Override
	public Object load(Serializable _id) throws ObjectNotFoundException {
		// utilisation de la méthode get au lieu de load car si on fait du
		// lazy loading avec Hibernate load ne lance pas de
		// ObjectRetrievalFailureException
		final Object obj = getHibernateTemplate().get(getClassBean(),
				(Serializable) _id);
		if (obj == null) {
			if (getLog().isInfoEnabled()) {
				getLog().info(
						"L'objet de type " + getClassBean()
								+ "  n'existe pas en base : " + _id);
			}
			throw new ObjectNotFoundException(_id, null);
		}
		return obj;
	}

	@Override
	public void create(Object _obj) {
		getHibernateTemplate().save(_obj);

	}

	@Override
	public void update(Object _obj) {
		getHibernateTemplate().update(_obj);

	}

	@Override
	public void saveOrUpdate(Object _obj) {
		getHibernateTemplate().saveOrUpdate(_obj);

	}

	@Override
	public void delete(Object _obj) {
		// forcer l'exécution du delete, sinon hibernate réorganise
		// l'ordre des requêtes (d'abord insert, puis update et delete).
		getHibernateTemplate().delete(_obj);
		getHibernateTemplate().flush();

	}

	@Override
	public void delete(Serializable _id) {

		try {
			// obtenir l'objet persistent avant de l'effacer
			delete(load(_id));
		} catch (ObjectNotFoundException e) {
			if (getLog().isInfoEnabled()) {
				getLog().info(
						"Impossible d'effacer l'objet de type "
								+ getClassBean() + " de cle " + _id);
			}
		}
	}

	@Override
	public List<?> findAll() {

		return getHibernateTemplate().loadAll(getClassBean());
	}

	@Override
	public void flush() {

		getHibernateTemplate().flush();
	}

	@Override
	public Object merge(Object _obj) {

		return getHibernateTemplate().merge(_obj);
	}

	@Override
	public int executeSqlUpdate(final String _sql) {

		return ((Integer) getHibernateTemplate().execute(
				new HibernateCallback() {
					@SuppressWarnings("deprecation")
					public Object doInHibernate(final Session _session)
							throws HibernateException, SQLException {
						Statement st = null;
						try {
							st = (Statement) _session.connection()
									.createStatement();
							final int result = st.executeUpdate(_sql);
							return new Integer(result);
						} finally {
							if (st != null)
								st.close();
						}
					}
				})).intValue();
	}

	@Override
	public int executeSqlUpdate(final String _sql, final Object[] _params) {
		return ((Integer) getHibernateTemplate().execute(
				new HibernateCallback() {
					@SuppressWarnings("deprecation")
					public Object doInHibernate(final Session _session)
							throws HibernateException, SQLException {
						PreparedStatement statement = null;
						try {
							statement = (PreparedStatement) _session
									.connection().prepareStatement(_sql);
							for (int i = 0; i < _params.length; i++) {
								statement.setObject(i + 1, _params[i]);
							}
							final int result = statement.executeUpdate();
							return new Integer(result);
						} finally {
							if (statement != null)
								statement.close();
						}
					}
				})).intValue();
	}

	@Override
	public int executeHibernateUpdate(final String _queryString,
			final String[] _paramNames, final Object[] _params) {

		return ((Integer) getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(final Session _session)
							throws HibernateException, SQLException {
						Query query = _session.createQuery(_queryString);
						for (int i = 0; i < _params.length; i++) {
							query.setParameter(_paramNames[i], _params[i]);
						}
						int result = query.executeUpdate();

						// faire un flush de la session hibernate
						// _session.flush();
						return new Integer(result);
					}
				})).intValue();
	}

	@Override
	public Query createQuery(String hqlQuery) {

		if (StringUtils.isEmpty(hqlQuery))
			return null;

		if (getSession() == null)
			throw new RuntimeException(
					"Current Hibernate Session is not available");

		try {
			return getSession().createQuery(hqlQuery);
		} catch (HibernateException e) {
			throw new RuntimeException("Can't create a Query : " + hqlQuery, e);
		}
	}

	@Override
	public SQLQuery createSQLQuery(String sqlQuery) {

		return null;
	}

	@Override
	public List<?> executeHQLQueryWithPagination(String _hqlQuery,
			String[] _paramNames, Object[] _paramValues, int page, int pageSize) {

		if (StringUtils.isEmpty(_hqlQuery))
			return null;

		if (page < 1)
			throw new RuntimeException("Wrong page number : " + page);

		Query q = createQuery(_hqlQuery);
		if (_paramNames != null && _paramValues != null) {
			for (int i = 0; i < _paramNames.length; i++) {
				q.setParameter(_paramNames[i], _paramValues[i]);
			}
		}
		q.setFirstResult((page - 1) * pageSize);
		q.setMaxResults(pageSize);

		return q.list();
	}

	@Override
	public List<?> executeSQLQueryWithPagination(String _sqlQuery,
			String[] _paramNames, Object[] _paramValues, int page, int pageSize) {

		if (StringUtils.isEmpty(_sqlQuery))
			return null;

		if (page < 1)
			throw new RuntimeException("Wrong page number : " + page);

		SQLQuery q = createSQLQuery(_sqlQuery);
		if (_paramNames != null && _paramValues != null) {
			for (int i = 0; i < _paramNames.length; i++) {
				q.setParameter(_paramNames[i], _paramValues[i]);
			}
		}
		q.setFirstResult((page - 1) * pageSize);
		q.setMaxResults(pageSize);

		return q.list();
	}

	@Override
	public int executeHqlUpdateQuery(String _hqlQuery)
			throws ApplicationException {

		return executeHqlUpdateQuery(_hqlQuery, ArrayUtils.EMPTY_STRING_ARRAY,
				ArrayUtils.EMPTY_OBJECT_ARRAY);
	}

	@Override
	public int executeHqlUpdateQuery(String _hqlQuery, String[] _paramNames,
			Object[] _paramValues) {

		if (StringUtils.isEmpty(_hqlQuery))
			return 0;

		Query q = createQuery(_hqlQuery);
		if (_paramNames != null && _paramValues != null) {
			for (int i = 0; i < _paramNames.length; i++) {
				q.setParameter(_paramNames[i], _paramValues[i]);
			}
		}
		return q.executeUpdate();
	}

	@Override
	public int executeSqlUpdateQuery(String _sqlQuery) {

		return executeSqlUpdate(_sqlQuery, ArrayUtils.EMPTY_OBJECT_ARRAY);
	}

	@Override
	public int executeSqlUpdateQuery(String _sqlQuery, String[] _paramNames,
			Object[] _paramValues) {

		if (StringUtils.isEmpty(_sqlQuery))
			return 0;

		SQLQuery q = createSQLQuery(_sqlQuery);
		if (_paramNames != null && _paramValues != null) {
			for (int i = 0; i < _paramNames.length; i++) {
				q.setParameter(_paramNames[i], _paramValues[i]);
			}
		}
		return q.executeUpdate();
	}

	@Override
	public Criteria createCriteria(Class<?> _persistentClass) {

		return getSession().createCriteria(_persistentClass);
	}

	@Override
	public Long getMaxId(String _entityName) {

		if (StringUtils.isEmpty(_entityName))
			return NumberUtils.LONG_ZERO;

		List<?> results = getHibernateTemplate().find(
				"SELECT MAX(id) FROM " + _entityName);
		if (results != null && results.size() > 0) {
			return (Long) results.get(0);
		}

		return NumberUtils.LONG_ZERO;
	}

	@Override
	public boolean existEntity(Class<?> _class, String _columnName,
			Object _valeur) {

		StringBuffer sBuffer = new StringBuffer();
		sBuffer.append("SELECT count(*) FROM  ");
		sBuffer.append(_class.getName());
		sBuffer.append(" where");
		sBuffer.append(" " + _columnName + " = " + _valeur);

		Query q = createQuery(sBuffer.toString());
		// q.setParameter(0, _valeur);

		List<?> results = q.list();

		if (results != null && results.size() > 0) {
			Integer i = new Integer(((Long) results.get(0)).intValue());
			if (i.intValue() == 0) {
				return false;
			} else {
				return true;
			}
		}
		return false;
	}

	@Override
	public void evictObjectsFromSession(List<?> objs) {

		if (objs == null || objs.size() == 0)
			return;

		for (int i = 0; i < objs.size(); i++) {
			getSession().evict(objs.get(i));
		}
	}

	@Override
	public void clearSession() {

		if (getSession() == null)
			throw new RuntimeException(
					"Current Hibernate Session is not available");
		getSession().clear();
	}

	@Override
	public DataSource getSlaveDataSource() {
		return this.m_slaveDataSource;
	}

	@Override
	public void setSlaveDataSource(DataSource dataSource) {
		this.m_slaveDataSource = dataSource;
	}

	@SuppressWarnings("deprecation")
	@Override
	public Connection getConnection() {
		Connection conn = null;
		try {
			if (getSlaveDataSource() != null) {
				conn = getSlaveDataSource().getConnection();
				masterDS = false;
			}
		} catch (Exception e) {
			logger.error("SlaveDataSource non disponible -> Connection obtenue de DataSource Master.");
			conn = getSession().connection();
		}

		if (conn == null) {
			conn = getSession().connection();
		}

		return conn;
	}

	@Override
	public boolean isMasterDS() {
		return this.masterDS;
	}

	@Override
	public int executeHibernateUpdate(String _sqlQuery) {

		return executeHibernateUpdate(_sqlQuery, ArrayUtils.EMPTY_STRING_ARRAY,
				ArrayUtils.EMPTY_OBJECT_ARRAY);
	}

}